package com.tool.emulator.apple2.wave;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;

/**
 * WaveOutputer.java @author tl 2011-6-26
 */
public class WaveOutputer {
	public static final int HEADER_LEN = 44;
	public static final int CHANNELS = 1;
	public static final int BITS = 8;
	public static final int WAVE_FREQ = 22050;//22.05K
	public static final int OFFSET_FILE_LEN = 4;
	public static final int OFFSET_DATA_LEN = 0x28;
	public static final int WAVE_VOL = 0xFF;
	public static final int FMT_LEN = 16;
	public static final int WAVE_FORMAT_PCM = 1;
	protected final OutputStream out;
	protected final long deviceFreq;
	protected int size = 0;
	protected long last_cycles = -1;
	protected int current_value = 0;

	public WaveOutputer(long deviceFreq, OutputStream out) throws IOException {
		super();
		this.out = out;
		this.deviceFreq = deviceFreq;
		this.writeHeader();
	}

	public static byte[] int2bytes(int val) {
		return int2bytes(val, new byte[4], 0);
	}

	public static byte[] int2bytes(int val, byte[] ar, int off) {
		ar[off++] = (byte) val;
		ar[off++] = (byte) (val >>> 8);
		ar[off++] = (byte) (val >>> 16);
		ar[off] = (byte) (val >>> 24);
		return ar;
	}

	public long getDeviceFreq() {
		return this.deviceFreq;
	}

	protected synchronized void writeHeader() throws IOException {
		this.size = 0;
		writeChars("RIFF");
		writeInt(0);
		writeChars("WAVE");
		writeChars("fmt ");
		writeInt(FMT_LEN);
		writeShort(WAVE_FORMAT_PCM);//PCM;
		writeShort(CHANNELS);//单声道;
		writeInt(WAVE_FREQ);//每秒样本数
		writeInt(WAVE_FREQ * CHANNELS * BITS / 8);//==FREQ;
		writeShort(CHANNELS * BITS / 8);//1;
		writeShort(BITS);
		writeChars("data");
		this.checkSize(OFFSET_DATA_LEN);
		writeInt(0);
		this.checkSize(HEADER_LEN);
		this.last_cycles = -1;
	}

	protected void checkSize(int expected) throws IOException {
		if (this.size != expected)
			throw new IOException("size " + this.size + " != " + expected);
	}

	protected void write(int b) throws IOException {
		this.size++;
		out.write(b);
	}

	protected void writeChars(String s) throws IOException {
		for (int i = 0; i < s.length(); i++)
			write(s.charAt(i));
	}

	protected void writeShort(int val) throws IOException {
		write(val & 0xff);
		write((val >>> 8) & 0xff);
	}

	protected void writeInt(int val) throws IOException {
		writeShort(val);
		writeShort(val >>> 16);
	}

	public synchronized int size() {
		return this.size;
	}

	public synchronized void flipFlop(long cycles) throws IOException {
		if (this.last_cycles < 0) {
			//first time;
			this.last_cycles = cycles;
			return;
		}
		writeWave(this.current_value, cycles - this.last_cycles);
		this.last_cycles = cycles;
		this.current_value = WAVE_VOL - this.current_value;
	}

	public synchronized int currentVol() {
		return this.current_value;
	}

	private void writeWave(int val, long delta_cycles) throws IOException {
		for (int i = (int) (delta_cycles * WAVE_FREQ / this.getDeviceFreq()); i >= 0; i--) {
			write(val);
		}
	}

	public synchronized int getFileLength() {
		return this.size - 8;
	}

	public synchronized int getDataLength() {
		return this.size - HEADER_LEN;
	}

	public synchronized void appendSpace(long time) throws IOException {
		int count = (int) (time * WAVE_FREQ / 1000L);
		for (int i = 0; i < count; i++)
			this.write(0);
		this.current_value = 0;
	}

	public synchronized void close() throws IOException {
		this.out.close();
	}

	public synchronized void updateFile(File file) throws IOException {
		RandomAccessFile rf = new RandomAccessFile(file, "rw");
		try {
			rf.seek(OFFSET_FILE_LEN);
			rf.write(int2bytes(this.getFileLength()));
			rf.seek(OFFSET_DATA_LEN);
			rf.write(int2bytes(this.getDataLength()));
		} finally {
			rf.close();
		}
	}

	public synchronized void updateBytes(byte[] waveData) {
		int2bytes(this.getFileLength(), waveData, OFFSET_FILE_LEN);
		int2bytes(this.getDataLength(), waveData, OFFSET_DATA_LEN);
	}

	public synchronized void closeAndUpdate(File file) throws IOException {
		this.close();
		this.updateFile(file);
	}
}
